﻿//-----------------------------------------------------------------------
// <copyright>
//     Copyright (c) Artur Mustafin. All rights reserved.
// </copyright>
//-----------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Linq;
using Math.Compiler.Classes;
using Math.Compiler.Interfaces;

#if DEBUG
using System.Text;
#endif

namespace Math.Compiler.Expressions
{
    internal class Function : MathExpression
    {
        private string _name;
        private readonly Func<double> _eval;

        public Function(string name, MathExpression args)
        {
            _name = name;
            Parameter parameter = args as Parameter;
            int functionCount = Externals.Functions.Count((f) => f.NamedAs(name));
            int enumerableCount = Externals.EnumerableFunctions.Count((f) => f.NamedAs(name));
            if (functionCount == 1)
            {
                IFunction function = Externals.Functions.First((f) => f.NamedAs(name));
                parameter.Initialize(function.Eval, this);
                _eval = () => Eval(function, EvaluateChildrens());
            }
            if (enumerableCount == 1)
            {
                IEnumerableFunction enumerable = Externals.EnumerableFunctions.First((f) => f.NamedAs(name));
                while (parameter != null)
                {
                    Add(parameter.MathExpression);
                    parameter = parameter.Next;
                }
                _eval = () => enumerable.Eval(EvaluateChildrens());
            }
            if (_eval == null)
            {
                throw new NotSupportedException("Initialization of the undifned function is not supported");
            }
        }

        private static double Eval(IFunction function, IEnumerable<double> values)
        {
            IEnumerator<double> enumerator = values.GetEnumerator();
            enumerator.MoveNext();
            double value = enumerator.Current;
            while (enumerator.MoveNext())
            {
                value = function.Eval(value, enumerator.Current);
            }
            return value;
        }

        private IEnumerable<double> EvaluateChildrens()
        {
            foreach (MathExpression expression in Children)
            {
                yield return expression.Eval();
            }
        }

        public override double Eval()
        {
            return _eval();
        }

#if DEBUG
        private string GetChildren()
        {
            StringBuilder sb = new StringBuilder();
            IEnumerator<MathExpression> enumerator = Children.GetEnumerator();
            if (enumerator.MoveNext())
            {
                sb.Append(enumerator.Current.ToString());
                while (enumerator.MoveNext())
                {
                    sb.AppendFormat(",{0}", enumerator.Current.ToString());
                }
            }
            return sb.ToString();
        }

        public override string ToString()
        {
            return string.Format("{0}({1})", _name, GetChildren());
        }
#endif

    }
}
